1 module tinyredis_util.util;
2 
3 import tinyredis : Redis, Response;
4 import std.datetime.systime : SysTime;
5 import std.experimental.logger;
6 
7 /**
8  * Set a Redis variable.
9  *
10  * Params:
11  *  redis = Database
12  *  key = Variable name
13  *  value = Variable value
14  */
15 void set(T)(Redis redis, string key, T value) {
16    static if (is(T == SysTime)) {
17       long unixTime = value.toUnixTime!long;
18       redis.send("SET", key, unixTime);
19    } else {
20       redis.send("SET", key, value);
21    }
22 }
23 
24 /**
25  * psetex works exactly like SETEX with the sole difference that the expire time is specified in milliseconds instead of seconds.
26  *
27  * Params:
28  *  redis = Database
29  *  key = Variable name
30  *  milliseconds = Expire time
31  *  value = Variable value
32  */
33 void psetex(T)(Redis redis, string key, int milliseconds, T value) {
34    redis.send("PSETEX", key, milliseconds, value);
35 }
36 
37 /**
38  * Set key to hold the string value and set key to timeout after a given number of seconds
39  *
40  * Params:
41  *  redis = Database
42  *  key = Variable name
43  *  seconds = Expire time
44  *  value = Variable value
45  */
46 void setex(T)(Redis redis, string key, int seconds, T value) {
47    redis.send("SETEX", key, seconds, value);
48 }
49 
50 /**
51  * Returns the value associated with field in the hash stored at key.
52  *
53  * Params:
54  *  redis = Database
55  *  key = Hash name
56  *  field = Field name
57  */
58 T hget(T)(Redis redis, string key, string field) {
59    static if (commonType!T) {
60       string reply = redis.send!string("HGET", key, field);
61       return conv!(T)(reply);
62    } else {
63       return redis.send!(T)("HGET", key, field);
64    }
65 }
66 
67 /**
68  * Get a Redis variable
69  *
70  * Params:
71  *  redis = Database
72  *  key = Variable name
73  */
74 T get(T)(Redis redis, string key) {
75    static if (commonType!T) {
76       string reply = redis.send!string("GET", key);
77       return conv!(T)(reply);
78    } else {
79       return redis.send!(T)("GET", key);
80    }
81 }
82 
83 unittest {
84    Redis redis = new Redis();
85    redis.send("select", 1);
86    redis.send("flushdb");
87    redis.send("HMSET", "hh", "a", 10, "b", 11);
88    redis.send("HSET", "hh", "c", "12");
89    int h0 = redis.hget!int("hh", "a");
90    assert(h0 == 10);
91 
92    string h1 = redis.hget!string("hh", "c");
93    assert(h1 == "12");
94    string h2 = redis.hget!string("hh", "b");
95    assert(h2 == "11");
96 }
97 
98 template commonType(T) {
99    enum commonType = (is(T == bool) || is(T == float) || is(T == double) || is(T == short) || is(T == int)
100             || is(T == long) || is(T == uint) || is(T == ulong) || is(T == string) || is(T == SysTime));
101 }
102 
103 /**
104  * Convert a string into T type.
105  *
106  *
107  */
108 T conv(T)(string input) if (commonType!T) {
109    import std.conv : to;
110    import std.string : isNumeric;
111    import std.datetime : DateTime;
112 
113    static if (is(T == double) || (is(T == float))) {
114       if (input.isNumeric) {
115          return input.to!(T);
116       } else {
117          return input == "true" ? 1. : 0.;
118       }
119    } else static if ((is(T == int)) || (is(T == long)) || (is(T == uint)) || (is(T == ulong))) {
120       if (input.isNumeric) {
121          return input.to!(double)
122             .to!(T);
123       } else {
124          return input == "true" ? 1 : 0;
125       }
126    } else static if (is(T == bool)) {
127       if (input.isNumeric) {
128          return input.to!(double) != 0.;
129       } else {
130          return input == "true" || input == "t";
131       }
132    } else static if (is(T == string)) {
133       return input;
134    } else static if (is(T == SysTime)) {
135       if (input.isNumeric) {
136          long unixTime = input.to!(double)
137             .to!long;
138          return SysTime.fromUnixTime(unixTime);
139       } else {
140          warning("empty datatime");
141          return SysTime(DateTime(1970, 1, 1, 1, 1, 1));
142       }
143    } else {
144       assert(false);
145    }
146 }
147 
148 @("getdouble")
149 unittest {
150    import std.datetime : DateTime;
151 
152    auto redis = new Redis("localhost", 6379);
153    redis.send("SELECT", 1);
154    redis.send("FLUSHDB");
155 
156    redis.send("SET", "delete_me", 3.14);
157    redis.send("SET", "delete:me", 3.15);
158    assert(redis.get!string("delete_me") == "3.14");
159    assert(redis.get!int("delete_me") == 3);
160    assert(redis.get!double("delete_me") == 3.14);
161    assert(redis.get!bool("delete_me"));
162 
163    assert(redis.get!string("delete:me") == "3.15");
164    assert(redis.get!int("delete:me") == 3);
165    assert(redis.get!double("delete:me") == 3.15);
166    assert(redis.get!bool("delete:me"));
167 
168    redis.send("SET", "delete_me", 0.0);
169    assert(redis.get!string("delete_me") == "0");
170    assert(redis.get!int("delete_me") == 0);
171    assert(redis.get!double("delete_me") == 0.);
172    assert(!redis.get!bool("delete_me"));
173 
174    redis.send("DEL", "delete_me");
175    assert(redis.get!double("delete_me") == 0.);
176 
177    redis.send("SET", "not_a_num", double.nan);
178 
179    import std.math : isNaN;
180 
181    assert(redis.get!double("not_a_num").isNaN);
182 
183    enum UT = 1_552_320_073;
184    redis.send("SET", "ut", UT);
185    auto expected = SysTime(DateTime(2019, 3, 11, 17, 01, 13));
186 
187    assert(redis.get!SysTime("ut") == expected);
188 }
189 
190 @("getint")
191 unittest {
192    auto redis = new Redis("localhost", 6379);
193    redis.send("SELECT", 1);
194    redis.send("FLUSHDB");
195 
196    redis.send("SET", "delete_me", 42);
197    assert(redis.get!string("delete_me") == "42");
198    assert(redis.get!int("delete_me") == 42);
199    assert(redis.get!uint("delete_me") == 42);
200    assert(redis.get!long("delete_me") == 42L);
201    assert(redis.get!ulong("delete_me") == 42uL);
202    assert(redis.get!size_t("delete_me") == 42uL);
203    assert(redis.get!double("delete_me") == 42.);
204    assert(redis.get!bool("delete_me"));
205 
206    redis.send("SET", "delete_me", 0);
207    assert(redis.get!string("delete_me") == "0");
208    assert(redis.get!int("delete_me") == 0);
209    assert(redis.get!long("delete_me") == 0L);
210    assert(redis.get!double("delete_me") == 0.);
211    assert(!redis.get!bool("delete_me"));
212 
213    redis.send("SET", "delete_me", -42);
214    assert(redis.get!string("delete_me") == "-42");
215    assert(redis.get!int("delete_me") == -42);
216    //redis.get!uint("delete_me") == 42); overflow
217    assert(redis.get!long("delete_me") == -42L);
218    assert(redis.get!double("delete_me") == -42.);
219    assert(redis.get!bool("delete_me"));
220 }
221 
222 @("getnull")
223 unittest {
224    auto redis = new Redis("localhost", 6379);
225    redis.send("SELECT", 1);
226    redis.send("FLUSHDB");
227 
228    assert(redis.get!string("none") == "");
229    assert(redis.get!int("none") == 0);
230    assert(redis.get!uint("none") == 0);
231    assert(redis.get!long("none") == 0L);
232    assert(redis.get!double("none") == 0.);
233    assert(!redis.get!bool("none"));
234 }
235 
236 @("getuint")
237 unittest {
238    auto redis = new Redis("localhost", 6379);
239    redis.send("SELECT", 1);
240    redis.send("FLUSHDB");
241 
242    redis.send("SET", "my_uint", cast(uint)42);
243    assert(redis.get!string("my_uint") == "42");
244    assert(redis.get!uint("my_uint") == 42);
245    assert(redis.get!int("my_uint") == 42);
246    assert(redis.get!long("my_uint") == 42L);
247    assert(redis.get!double("my_uint") == 42.);
248    assert(redis.get!bool("my_uint"));
249 
250    redis.send("SET", "my_uint", cast(uint)0);
251    assert(redis.get!string("my_uint") == "0");
252    assert(redis.get!int("my_uint") == 0);
253    assert(redis.get!long("my_uint") == 0L);
254    assert(redis.get!double("my_uint") == 0.);
255    assert(!redis.get!bool("my_uint"));
256 }
257 
258 @("getlong")
259 unittest {
260    auto redis = new Redis("localhost", 6379);
261 
262    redis.send("SELECT", 1);
263    redis.send("FLUSHDB");
264 
265    redis.send("SET", "delete_me", 42L);
266    assert(redis.get!string("delete_me") == "42");
267    assert(redis.get!int("delete_me") == 42);
268    assert(redis.get!long("delete_me") == 42L);
269    assert(redis.get!double("delete_me") == 42.);
270    assert(redis.get!bool("delete_me"));
271 
272    redis.send("SET", "delete_me", 0L);
273    assert(redis.get!string("delete_me") == "0");
274    assert(redis.get!int("delete_me") == 0);
275    assert(redis.get!long("delete_me") == 0L);
276    assert(redis.get!double("delete_me") == 0.);
277    assert(!redis.get!bool("delete_me"));
278 }
279 
280 @("getbool")
281 unittest {
282    Redis redis = new Redis("localhost", 6379);
283    redis.send("SELECT", 1);
284    redis.send("FLUSHDB");
285 
286    redis.send("SET", "delete_me", true);
287    assert(redis.get!string("delete_me") == "true");
288    assert(redis.get!int("delete_me") == 1);
289    assert(redis.get!long("delete_me") == 1L);
290    assert(redis.get!double("delete_me") == 1.);
291    assert(redis.get!bool("delete_me"));
292 
293    redis.send("SET", "delete_me", false);
294    assert(redis.get!string("delete_me") == "false");
295    assert(redis.get!int("delete_me") == 0);
296    assert(redis.get!long("delete_me") == 0L);
297    assert(redis.get!double("delete_me") == 0.);
298    assert(!redis.get!bool("delete_me"));
299 
300    redis.send("SET", "bool_as_string", "true");
301    assert(redis.get!bool("bool_as_string"));
302    redis.send("SET", "bool_as_string", "t");
303    assert(redis.get!bool("bool_as_string"));
304    redis.send("SET", "bool_as_string", "f");
305    assert(!redis.get!bool("bool_as_string"));
306 }
307 
308 @("getstring")
309 unittest {
310    auto redis = new Redis("localhost", 6379);
311    redis.send("SELECT", 1);
312    redis.send("FLUSHDB");
313 
314    redis.send("SET", "delete_me", "true");
315    assert(redis.get!string("delete_me") == "true");
316    assert(redis.get!int("delete_me") == 1);
317    assert(redis.get!long("delete_me") == 1L);
318    assert(redis.get!double("delete_me") == 1.);
319    assert(redis.get!bool("delete_me"));
320 
321    redis.send("SET", "delete_me", "false");
322    assert(redis.get!string("delete_me") == "false");
323    assert(redis.get!int("delete_me") == 0);
324    assert(redis.get!long("delete_me") == 0L);
325    assert(redis.get!double("delete_me") == 0.);
326    assert(!redis.get!bool("delete_me"));
327 
328    redis.send("SET", "delete_me", "cul");
329    assert(redis.get!string("delete_me") == "cul");
330    assert(redis.get!int("delete_me") == 0);
331    assert(redis.get!long("delete_me") == 0L);
332    assert(redis.get!double("delete_me") == 0.);
333    assert(!redis.get!bool("delete_me"));
334 
335    redis.send("SET", "delete_me", "42");
336    assert(redis.get!string("delete_me") == "42");
337    assert(redis.get!int("delete_me") == 42);
338    assert(redis.get!long("delete_me") == 42L);
339    assert(redis.get!double("delete_me") == 42.);
340    assert(redis.get!bool("delete_me"));
341 
342    redis.send("SET", "delete_me", "3.14");
343    assert(redis.get!string("delete_me") == "3.14");
344    assert(redis.get!int("delete_me") == 3);
345    assert(redis.get!double("delete_me") == 3.14);
346    assert(redis.get!bool("delete_me"));
347 
348    redis.send("SET", "delete_me", "3,14");
349    assert(redis.get!string("delete_me") == "3,14");
350    assert(redis.get!int("delete_me") == 0);
351    assert(redis.get!double("delete_me") == 0.);
352    assert(!redis.get!bool("delete_me"));
353 }
354 
355 @("gettime")
356 unittest {
357    import std.datetime : DateTime;
358    import std.datetime.systime : SysTime;
359 
360    auto redis = new Redis();
361    redis.send("SELECT", 1);
362    redis.send("FLUSHDB");
363 
364    enum UT = 1_552_320_073;
365    redis.send("SET", "ut", UT);
366    auto expected = SysTime(DateTime(2019, 3, 11, 17, 01, 13));
367 
368    assert(redis.get!SysTime("ut") == expected);
369 
370    redis.set!SysTime("ut1", expected);
371    assert(redis.get!long("ut1") == UT);
372    redis.send("SET", "ut", "");
373    auto epoch = SysTime(DateTime(1970, 1, 1, 1, 1, 1));
374    assert(redis.get!SysTime("ut") == epoch);
375 }
376 /**
377  * Returns the bit value at offset in the string value stored at key.
378  */
379 bool getBit(Redis redis, string key, uint offset) {
380    return redis.send("GETBIT", key, offset).toBool;
381 }
382 
383 /**
384  * Sets or clears the bit at offset in the string value stored at key.
385  * The bit is either set or cleared depending on value, which can be either 0 or 1.
386  *
387  * Params:
388  *  redis = Database
389  *  key = Key
390  *  offset = Bit to set or reset
391  */
392 void setBit(Redis redis, string key, uint offset, bool value) {
393    redis.send("SETBIT", key, offset, value ? 1 : 0);
394 }
395 
396 /**
397  * Tests and sets (sets to 1) the bit.
398  *
399  * Internally use `SETBIT` function.
400  */
401 bool bts(Redis redis, string key, uint bitnum) {
402    bool b = redis.getBit(key, bitnum);
403    redis.send("SETBIT", key, bitnum, 1);
404    return b;
405 }
406 
407 /**
408  * Tests and resets (sets to 0) the bit.
409  *
410  * Internally use `SETBIT` function.
411  */
412 bool btr(Redis redis, string key, uint bitnum) {
413    bool b = redis.getBit(key, bitnum);
414    redis.send("SETBIT", key, bitnum, 0);
415    return b;
416 }
417 
418 ///
419 unittest {
420    auto redis = new Redis();
421    redis.send("SELECT", 1);
422    redis.send("FLUSHDB");
423 
424    redis.setBit("bf", 3, true);
425    assert(redis.getBit("bf", 3));
426 
427    redis.setBit("bf", 0, true);
428    assert(redis.getBit("bf", 0));
429    assert(!redis.getBit("bf", 1));
430    assert(!redis.getBit("bf", 2));
431    assert(redis.getBit("bf", 3));
432 
433    redis.btr("bf", 0);
434    assert(!redis.getBit("bf", 0));
435 
436    redis.btr("bf", 3);
437    assert(!redis.getBit("bf", 3));
438 }
439 
440 /**
441  * Safe convert Response
442  */
443 T respTo(T)(Response response) {
444    import std.conv : to;
445    import std.string : isNumeric;
446    import std.datetime.systime : SysTime;
447 
448    static if (is(T == double) || (is(T == float))) {
449       string reply = response.toString;
450       if (reply.isNumeric) {
451          return reply.to!(T);
452       } else {
453          return reply == "true" ? 1. : 0.;
454       }
455    } else static if ((is(T == int)) || (is(T == long)) || (is(T == uint)) || (is(T == ulong))) {
456       string reply = response.toString;
457       if (reply.isNumeric) {
458          return reply.to!(double)
459             .to!(T);
460       } else {
461          return reply == "true" ? 1 : 0;
462       }
463    } else static if (is(T == bool)) {
464       string reply = response.toString;
465       if (reply.isNumeric) {
466          return reply.to!(double) != 0.;
467       } else {
468          return reply == "true";
469       }
470    } else {
471       return response.toString.to!T;
472    }
473 }
474 
475 @("respTobool")
476 unittest {
477    Redis redis = new Redis("localhost", 6379);
478    redis.send("SELECT", 1);
479    redis.send("FLUSHDB");
480 
481    redis.send("SET", "delete_me", true);
482    assert(redis.send("GET", "delete_me").respTo!string == "true");
483    assert(redis.send("GET", "delete_me").respTo!int == 1);
484    assert(redis.send("GET", "delete_me").respTo!long == 1L);
485    assert(redis.send("GET", "delete_me").respTo!double == 1.);
486    assert(redis.send("GET", "delete_me").respTo!bool);
487 
488    redis.send("SET", "delete_me", false);
489    assert(redis.send("GET", "delete_me").respTo!string == "false");
490    assert(redis.send("GET", "delete_me").respTo!int == 0);
491    assert(redis.send("GET", "delete_me").respTo!long == 0L);
492    assert(redis.send("GET", "delete_me").respTo!double == 0.);
493    assert(!redis.send("GET", "delete_me").respTo!bool);
494 }
495 
496 @("respToString")
497 unittest {
498    auto redis = new Redis("localhost", 6379);
499    redis.send("SELECT", 1);
500    redis.send("FLUSHDB");
501 
502    redis.send("SET", "delete_me", "true");
503    assert(redis.send("GET", "delete_me").respTo!string == "true");
504    assert(redis.send("GET", "delete_me").respTo!int == 1);
505    assert(redis.send("GET", "delete_me").respTo!long == 1L);
506    assert(redis.send("GET", "delete_me").respTo!double == 1.);
507    assert(redis.send("GET", "delete_me").respTo!bool);
508 
509    redis.send("SET", "delete_me", "false");
510    assert(redis.send("GET", "delete_me").respTo!string == "false");
511    assert(redis.send("GET", "delete_me").respTo!int == 0);
512    assert(redis.send("GET", "delete_me").respTo!long == 0L);
513    assert(redis.send("GET", "delete_me").respTo!double == 0.);
514    assert(!redis.send("GET", "delete_me").respTo!bool);
515 
516    redis.send("SET", "delete_me", "cul");
517    assert(redis.send("GET", "delete_me").respTo!string == "cul");
518    assert(redis.send("GET", "delete_me").respTo!int == 0);
519    assert(redis.send("GET", "delete_me").respTo!long == 0L);
520    assert(redis.send("GET", "delete_me").respTo!double == 0.);
521    assert(!redis.send("GET", "delete_me").respTo!bool);
522 
523    redis.send("SET", "delete_me", "42");
524    assert(redis.send("GET", "delete_me").respTo!string == "42");
525    assert(redis.send("GET", "delete_me").respTo!int == 42);
526    assert(redis.send("GET", "delete_me").respTo!long == 42L);
527    assert(redis.send("GET", "delete_me").respTo!double == 42.);
528    assert(redis.send("GET", "delete_me").respTo!bool);
529 
530    redis.send("SET", "delete_me", "3.14");
531    assert(redis.send("GET", "delete_me").respTo!string == "3.14");
532    assert(redis.send("GET", "delete_me").respTo!int == 3);
533    assert(redis.send("GET", "delete_me").respTo!double == 3.14);
534    assert(redis.send("GET", "delete_me").respTo!bool);
535 
536    redis.send("SET", "delete_me", "3,14");
537    assert(redis.send("GET", "delete_me").respTo!string == "3,14");
538    assert(redis.send("GET", "delete_me").respTo!int == 0);
539    assert(redis.send("GET", "delete_me").respTo!double == 0.);
540    assert(!redis.send("GET", "delete_me").respTo!bool);
541 }
542 
543 @("respToDouble")
544 unittest {
545    auto redis = new Redis("localhost", 6379);
546    redis.send("SELECT", 1);
547    redis.send("FLUSHDB");
548 
549    redis.send("SET", "delete_me", 3.14);
550    assert(redis.send("GET", "delete_me").respTo!string == "3.14");
551    assert(redis.send("GET", "delete_me").respTo!int == 3);
552    assert(redis.send("GET", "delete_me").respTo!double == 3.14);
553    assert(redis.send("GET", "delete_me").respTo!bool);
554 
555    redis.send("SET", "delete:me", 3.15);
556    assert(redis.send("GET", "delete:me").respTo!string == "3.15");
557    assert(redis.send("GET", "delete:me").respTo!int == 3);
558    assert(redis.send("GET", "delete:me").respTo!double == 3.15);
559    assert(redis.send("GET", "delete:me").respTo!bool);
560 
561    redis.send("SET", "delete_me", 0.0);
562    assert(redis.send("GET", "delete_me").respTo!string == "0");
563    assert(redis.send("GET", "delete_me").respTo!int == 0);
564    assert(redis.send("GET", "delete_me").respTo!double == 0.);
565    assert(!redis.send("GET", "delete_me").respTo!bool);
566    redis.send("DEL", "delete_me");
567    assert(redis.send("GET", "delete_me").respTo!double == 0.);
568 
569    redis.send("SET", "not_a_num", double.nan);
570    import std.math : isNaN;
571 
572    assert(redis.send("GET", "not_a_num").respTo!double.isNaN);
573 }
574 
575 /**
576  * Copy a structure into Redis variables.
577  *
578  * Examples:
579  * If the structure is:
580  * ```
581  * struct Foo {
582  *   int intParm;
583  *   string stringParm;
584  *   bool is60Hz
585  * }
586  * ```
587  *
588  * Then
589  * ```
590  * Foo foo;
591  * copyToRedis!Foo(foo, redis, "f:")
592  * ```
593  *
594  * set these redis variables:
595  *
596  * $(LIST
597  *   * f:int_parm
598  *   * f:string_parm
599  *   * f:is60_hz ATTENTION between letter and number does not add underscore
600  * )
601  *
602  * Params:
603  * Params:
604  *  source = Structure to copy
605  *  target = Database in which to copy the structure
606  *  prefix = Prefix to be added to the structure members
607  */
608 void copyToRedis(T)(T source, Redis target, string prefix) {
609    import std.traits : hasMember, isBasicType, isSomeString, FieldNameTuple;
610 
611    foreach (member; FieldNameTuple!T) {
612       auto value = __traits(getMember, source, member);
613       string name = member.camelCaseToSnake;
614       /*
615        * l'assegnazione:
616        * target[k ~ member] = value;
617        * e' eseguita a compile-time, se si omette static si ha un errore tentando di assegnare `let` ad un Parm
618        */
619       static if (isBasicType!(typeof(value)) || isSomeString!(typeof(value))) {
620          target.set(prefix ~ name, value);
621       }
622    }
623 }
624 
625 @("test_copy_pre")
626 unittest {
627    Redis redis = new Redis("localhost", 6379);
628 
629    struct DummyData {
630       string condition;
631       string loggerName;
632       int noOfIteration;
633       double duration;
634       bool visible;
635       string[] lists;
636    }
637 
638    DummyData t = {
639       condition: "aa", loggerName: "DD", visible: true, noOfIteration: 42, duration: 19.64, lists: ["a", "b"]
640    };
641 
642    t.copyToRedis!DummyData(redis, "cu_");
643    assert(redis.get!string("cu_condition") == "aa");
644    assert(redis.get!string("cu_logger_name") == "DD");
645    assert(redis.get!bool("cu_visible"));
646    assert(redis.get!int("cu_no_of_iteration") == 42);
647    assert(redis.get!double("cu_duration") == 19.64);
648    // gli array sono ignorati
649    assert(redis.get!string("lists") == "");
650 
651    assert(redis.get!string("adasdadwerwerwerwer") == "");
652 }
653 
654 /**
655  * Copy the values of the Redis variables into a structure.
656  *
657  * The names of the redis variables to be used are the names of the members in snake_case with optional prefix.
658  *
659  * If T is a structure:
660  * ```
661  * struct Foo {
662  *   int fooName;
663  * }
664  * ```
665  *
666  * Then `copyToStruct` copies the value of the variable `<prefix>foo_name` to foo.fooName
667  *
668  *
669  * Params:
670  *  redis = Database from which to read the variables
671  *  target = Structure that receives the values
672  *  prefix = Prefix to be added to the variable names
673  */
674 void copyToStruct(T)(Redis redis, ref T target, string prefix) {
675    import std.traits : hasMember;
676 
677    foreach (member; __traits(allMembers, T)) {
678       auto m = __traits(getMember, target, member);
679       string name = member.camelCaseToSnake;
680 
681       static if (is(typeof(m) == int)) {
682          __traits(getMember, target, member) = redis.get!int(prefix ~ name);
683       } else static if (is(typeof(m) == long)) {
684          __traits(getMember, target, member) = redis.get!long(prefix ~ name);
685       } else static if (is(typeof(m) == double)) {
686          __traits(getMember, target, member) = redis.get!double(prefix ~ name);
687       } else static if (is(typeof(m) == bool)) {
688          __traits(getMember, target, member) = redis.get!bool(prefix ~ name);
689       } else static if (is(typeof(m) == string)) {
690          __traits(getMember, target, member) = redis.get!string(prefix ~ name);
691       }
692    }
693 }
694 
695 ///
696 @("copy2struct")
697 unittest {
698    Redis redis = new Redis("localhost", 6379);
699    redis.send("SELECT", 1);
700    redis.send("FLUSHDB");
701    redis.set!string("cu_condition", "aa");
702    redis.set!string("cu_logger_name", "DD");
703    redis.set!bool("cu_visible", true);
704    redis.set!int("cu_no_of_iteration", 42);
705    redis.set!double("cu_duration", 19.64);
706 
707    struct DummyData {
708       string condition;
709       string loggerName;
710       int noOfIteration;
711       double duration;
712       bool visible;
713       string[] lists;
714    }
715 
716    DummyData dummy;
717    redis.copyToStruct(dummy, "cu_");
718    assert(dummy.condition == "aa");
719    assert(dummy.loggerName == "DD");
720    assert(dummy.noOfIteration == 42);
721    assert(dummy.duration == 19.64);
722    assert(dummy.lists.length == 0);
723 }
724 
725 /**
726  * Convert a lower camelcase string to snake case.
727  * We can't use regex to match at compile-time so we'll iterate through the string and convert it manually.
728  *
729  * See_Also:
730  * [see util.d](https://github.com/jjpatel/dhtags)
731  */
732 string camelCaseToSnake(in string s) @safe pure {
733    import std.array : join;
734    import std.range : enumerate;
735    import std.algorithm : map;
736    import std.ascii : isUpper, isLower, isDigit;
737    import std.string : toLower;
738    import std.conv : to;
739 
740    return s.enumerate.map!((t) {
741       if (isUpper(t.value)) {
742          if (t.index > 0 && (isLower(s[t.index - 1]) || isDigit(s[t.index - 1]) || (t.index < s.length - 1 && isLower(s[t.index + 1])))) {
743             return "_" ~ t.value.toLower.to!string;
744          } else {
745             return t.value.toLower.to!string;
746          }
747       } else {
748          return t.value.to!string;
749       }
750    }).join;
751 }
752 
753 ///
754 @("snake")
755 unittest {
756    assert("ABCD".camelCaseToSnake == "abcd");
757    assert("A0CD".camelCaseToSnake == "a0_cd");
758    assert("aBcD".camelCaseToSnake == "a_bc_d");
759    assert("aBcDE".camelCaseToSnake == "a_bc_de");
760    assert("a0CDe".camelCaseToSnake == "a0_c_de");
761    assert("abCDe".camelCaseToSnake == "ab_c_de");
762    assert("aBc1".camelCaseToSnake == "a_bc1");
763    assert("xABy".camelCaseToSnake == "x_a_by");
764    assert("caccaPipiPuzzetta".camelCaseToSnake == "cacca_pipi_puzzetta");
765    assert("vacuum0PThreshold".camelCaseToSnake == "vacuum0_p_threshold");
766    assert("vacuum0PressThreshold".camelCaseToSnake == "vacuum0_press_threshold");
767    assert("".camelCaseToSnake == "");
768 
769    assert("cop3pAvg".camelCaseToSnake == "cop3p_avg");
770    assert("cop3p".camelCaseToSnake == "cop3p");
771    assert("vSupply3pMeas".camelCaseToSnake == "v_supply3p_meas");
772    assert("pDisAtTCondSp".camelCaseToSnake == "p_dis_at_t_cond_sp");
773    assert("res8r1".camelCaseToSnake == "res8r1");
774    assert("pid00run".camelCaseToSnake == "pid00run");
775 }